<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - interactive - raycasting - points</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				color: #ffffff;
				background-color: #000000;
				margin: 0px;
				overflow: hidden;
			}
			#info {
				position: absolute;
				top: 0px;
				width: 100%;
				padding: 5px;
				font-family: Monospace;
				font-size: 13px;
				text-align: center;
				font-weight: bold;
			}
			a {
				color: #fff;
			}
		</style>
	</head>

	<body>
		<div id="container"></div>
		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> webgl - interactive - raycasting - points </div>

		<script src="./build/three.js"></script>

		<script src="js/Detector.js"></script>
		<script src="js/libs/stats.min.js"></script>

		<script>

			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

			var renderer, scene, camera, stats;
			var pointclouds;
			var raycaster;
			var mouse = new THREE.Vector2();
			var intersection = null;
			var spheres = [];
			var spheresIndex = 0;
			var clock;

			var threshold = 0.1;
			var pointSize = 0.05;
			var width = 150;
			var length = 150;
			var rotateY = new THREE.Matrix4().makeRotationY( 0.005 );

			init();
			animate();

			function generatePointCloudGeometry( color, width, length ){

				var geometry = new THREE.BufferGeometry();
				var numPoints = width*length;

				var positions = new Float32Array( numPoints*3 );
				var colors = new Float32Array( numPoints*3 );

				var k = 0;

				for( var i = 0; i < width; i++ ) {

					for( var j = 0; j < length; j++ ) {

						var u = i / width;
						var v = j / length;
						var x = u - 0.5;
						var y = ( Math.cos( u * Math.PI * 8 ) + Math.sin( v * Math.PI * 8 ) ) / 20;
						var z = v - 0.5;

						positions[ 3 * k ] = x;
						positions[ 3 * k + 1 ] = y;
						positions[ 3 * k + 2 ] = z;

						var intensity = ( y + 0.1 ) * 5;
						colors[ 3 * k ] = color.r * intensity;
						colors[ 3 * k + 1 ] = color.g * intensity;
						colors[ 3 * k + 2 ] = color.b * intensity;

						k++;

					}

				}

				geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
				geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
				geometry.computeBoundingBox();

				return geometry;

			}

			function generatePointcloud( color, width, length ) {

				var geometry = generatePointCloudGeometry( color, width, length );

				var material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
				var pointcloud = new THREE.Points( geometry, material );

				return pointcloud;

			}

			function generateIndexedPointcloud( color, width, length ) {

				var geometry = generatePointCloudGeometry( color, width, length );
				var numPoints = width * length;
				var indices = new Uint16Array( numPoints );

				var k = 0;

				for( var i = 0; i < width; i++ ) {

					for( var j = 0; j < length; j++ ) {

						indices[ k ] = k;
						k++;

					}

				}

				geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );

				var material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
				var pointcloud = new THREE.Points( geometry, material );

				return pointcloud;

			}

			function generateIndexedWithOffsetPointcloud( color, width, length ){

				var geometry = generatePointCloudGeometry( color, width, length );
				var numPoints = width * length;
				var indices = new Uint16Array( numPoints );

				var k = 0;

				for( var i = 0; i < width; i++ ){

					for( var j = 0; j < length; j++ ) {

						indices[ k ] = k;
						k++;

					}

				}

				geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );
				geometry.addGroup( 0, indices.length );

				var material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
				var pointcloud = new THREE.Points( geometry, material );

				return pointcloud;

			}

			function generateRegularPointcloud( color, width, length ) {

				var geometry = new THREE.Geometry();

				var colors = [];

				var k = 0;

				for( var i = 0; i < width; i++ ) {

					for( var j = 0; j < length; j++ ) {

						var u = i / width;
						var v = j / length;
						var x = u - 0.5;
						var y = ( Math.cos( u * Math.PI * 8 ) + Math.sin( v * Math.PI * 8) ) / 20;
						var z = v - 0.5;
						var v = new THREE.Vector3( x,y,z );

						var intensity = ( y + 0.1 ) * 7;
						colors[ 3 * k ] = color.r * intensity;
						colors[ 3 * k + 1 ] = color.g * intensity;
						colors[ 3 * k + 2 ] = color.b * intensity;

						geometry.vertices.push( v );
						colors[ k ] = ( color.clone().multiplyScalar( intensity ) );

						k++;

					}

				}

				geometry.colors = colors;
				geometry.computeBoundingBox();

				var material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
				var pointcloud = new THREE.Points( geometry, material );

				return pointcloud;

			}

			function init() {

				var container = document.getElementById( 'container' );

				scene = new THREE.Scene();

				clock = new THREE.Clock();

				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
				camera.applyMatrix( new THREE.Matrix4().makeTranslation( 0,0,20 ) );
				camera.applyMatrix( new THREE.Matrix4().makeRotationX( -0.5 ) );

				//

				var pcBuffer = generatePointcloud( new THREE.Color( 1,0,0 ), width, length );
				pcBuffer.scale.set( 10,10,10 );
				pcBuffer.position.set( -5,0,5 );
				scene.add( pcBuffer );

				var pcIndexed = generateIndexedPointcloud( new THREE.Color( 0,1,0 ), width, length );
				pcIndexed.scale.set( 10,10,10 );
				pcIndexed.position.set( 5,0,5 );
				scene.add( pcIndexed );

				var pcIndexedOffset = generateIndexedWithOffsetPointcloud( new THREE.Color( 0,1,1 ), width, length );
				pcIndexedOffset.scale.set( 10,10,10 );
				pcIndexedOffset.position.set( 5,0,-5 );
				scene.add( pcIndexedOffset );

				var pcRegular = generateRegularPointcloud( new THREE.Color( 1,0,1 ), width, length );
				pcRegular.scale.set( 10,10,10 );
				pcRegular.position.set( -5,0,-5 );
				scene.add( pcRegular );

				pointclouds = [ pcBuffer, pcIndexed, pcIndexedOffset, pcRegular ];

				//

				var sphereGeometry = new THREE.SphereGeometry( 0.1, 32, 32 );
				var sphereMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, shading: THREE.FlatShading } );

				for ( var i = 0; i < 40; i++ ) {

					var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
					scene.add( sphere );
					spheres.push( sphere );

				}

				//

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				container.appendChild( renderer.domElement );

				//

				raycaster = new THREE.Raycaster();
				raycaster.params.Points.threshold = threshold;

				//

				stats = new Stats();
				container.appendChild( stats.dom );

				//

				window.addEventListener( 'resize', onWindowResize, false );
				document.addEventListener( 'mousemove', onDocumentMouseMove, false );

			}

			function onDocumentMouseMove( event ) {

				event.preventDefault();

				mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
				mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();

			}

			var toggle = 0;

			function render() {

				camera.applyMatrix( rotateY );
				camera.updateMatrixWorld();

				raycaster.setFromCamera( mouse, camera );

				var intersections = raycaster.intersectObjects( pointclouds );
				intersection = ( intersections.length ) > 0 ? intersections[ 0 ] : null;

				if ( toggle > 0.02 && intersection !== null) {

					spheres[ spheresIndex ].position.copy( intersection.point );
					spheres[ spheresIndex ].scale.set( 1, 1, 1 );
					spheresIndex = ( spheresIndex + 1 ) % spheres.length;

					toggle = 0;

				}

				for ( var i = 0; i < spheres.length; i++ ) {

					var sphere = spheres[ i ];
					sphere.scale.multiplyScalar( 0.98 );
					sphere.scale.clampScalar( 0.01, 1 );

				}

				toggle += clock.getDelta();

				renderer.render( scene, camera );

			}

		</script>

	</body>

</html>
